/*
 * Copyright (c) 2010 Vadim Zhukov <persgray@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


#include "common.h"

#ifdef HAS_BSD_SENSORS

#include <sys/param.h>
#include <sys/sensors.h>
#include <sys/sysctl.h>
#include <sys/wait.h>

#include <ctype.h>
#include <errno.h>
#include <kvm.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

#include "logmsg.h"
#include "sensor.h"

static struct sensordev	*bsd_sensdevs;
static size_t		 bsd_sensdevs_cnt;


static int	sensor_bsd_load(void);


/*
 * Load list of kernel devices with sensors.
 *
 * Return 0 on success, -1 on failure.
 */
int
sensor_bsd_load(void) {
	struct sensordev	*bss;
	size_t			 len;
	int			 oerrno, mib[2] = { CTL_HW, HW_SENSORS };

	bss = NULL;
	if (sysctl(mib, 2, NULL, &len, NULL, 0) == -1)
		goto err;
alloc:
	bss = malloc(len);
	if (bss == NULL)
		goto err;
	bzero(bss, len);
	if (sysctl(mib, 2, bss, &len, NULL, 0) == -1) {
		if (errno == ENOMEM) {
			free(bss);
			goto alloc;
		}
		goto err;
	}
	free(bsd_sensdevs);
	bsd_sensdevs = bss;
	bsd_sensdevs_cnt = len / sizeof(struct sensordev);
	return 0;

err:
	oerrno = errno;
	free(bss);
	errno = oerrno;
	return -1;
}

/*
 * Initialize BSD sensor: split name to device name, sensor type and index.
 */
int
sensor_bsd_init(struct wsensor *s) {
	size_t		 i;
	const char	*end, *errstr, *p;

	if (bsd_sensdevs == NULL)
		if (sensor_bsd_load() == -1)
			return -1;
	end = strchr(s->name, '/');	/* Sure it exists, see sensor_init() */
	end = strchr(end, '/');
	if (end == NULL) {
		errno = EINVAL;
		return -1;
	}

	p = end + 1;
	end = strchr(p, '/');
	if (end == NULL) {
		errno = EINVAL;
		return -1;
	}
	for (i = 0; i < bsd_sensdevs_cnt; i++) {
		if (strlen(bsd_sensdevs[i].xname) != end - p)
			continue;
		if (memcmp(bsd_sensdevs[i].xname, p, end - p))
			continue;
		goto dev_found;
	}
	errno = ENOENT;
	return -1;

dev_found:
	s->sp.sp_bsd.dev = i;
	p = end + 1;
	end = strchr(p, '/');
	for (i = 0; i < SENSOR_MAX_TYPES; i++) {
		if (strlen(sensor_type_s[i]) != end - p)
			continue;
		if (memcmp(sensor_type_s[i], p, end - p))
			continue;
		goto type_found;
	}
	errno = ENOENT;
	return -1;

type_found:
	if (bsd_sensdevs[s->sp.sp_bsd.dev].maxnumt[i] == 0) {
		errno = ENOENT;
		return -1;
	}
	s->sp.sp_bsd.stype = i;
	p = end + 1;
	if (!isdigit(*p)) {
		errno = EINVAL;
		return -1;
	}
	s->sp.sp_bsd.sindex = (int)strtonum(p, 0,
	    bsd_sensdevs[s->sp.sp_bsd.dev].maxnumt[s->sp.sp_bsd.stype] - 1,
	    &errstr);
	if (errstr == NULL) {
		errno = EINVAL;
		return -1;
	}
	
	return 0;
}

int
sensor_bsd_get(struct wsensor *s, struct log_msg *m) {
	struct sensor	 ks;
	size_t		 len;
	int		 n, mib[5] = { CTL_HW, HW_SENSORS, s->sp.sp_bsd.dev,
	    s->sp.sp_bsd.stype, s->sp.sp_bsd.sindex };

	len = sizeof(struct sensor);
	if (sysctl(mib, nitems(mib), &ks, &len, NULL, 0) == -1)
		return -1;

	n = 0;
	/* The following huge switch was adapted from sysctl(1) in OpenBSD */
	switch (s->sp.sp_bsd.stype) {
	case SENSOR_TEMP:
		n = asprintf(&m->text, "%.2f degC",
			(ks.value - 273150000) / 1000000.0);
		break;
	case SENSOR_FANRPM:
		n = asprintf(&m->text, "%lld RPM", ks.value);
		break;
	case SENSOR_VOLTS_DC:
		n = asprintf(&m->text, "%.2f VDC", ks.value / 1000000.0);
		break;
	case SENSOR_AMPS:
		n = asprintf(&m->text, "%.2f A", ks.value / 1000000.0);
		break;
	case SENSOR_WATTHOUR:
		n = asprintf(&m->text, "%.2f Wh", ks.value / 1000000.0);
		break;
	case SENSOR_AMPHOUR:
		n = asprintf(&m->text, "%.2f Ah", ks.value / 1000000.0);
		break;
	case SENSOR_INDICATOR:
		n = asprintf(&m->text, "%s", ks.value ? "On" : "Off");
		break;
	case SENSOR_INTEGER:
		n = asprintf(&m->text, "%lld", ks.value);
		break;
	case SENSOR_PERCENT:
		n = asprintf(&m->text, "%.2f%%", ks.value / 1000.0);
		break;
	case SENSOR_LUX:
		n = asprintf(&m->text, "%.2f lx", ks.value / 1000000.0);
		break;
	case SENSOR_DRIVE:
		switch (ks.value) {
		case SENSOR_DRIVE_EMPTY:
			m->text = strdup("empty");
			break;
		case SENSOR_DRIVE_READY:
			m->text = strdup("ready");
			break;
		case SENSOR_DRIVE_POWERUP:
			m->text = strdup("powering up");
			break;
		case SENSOR_DRIVE_ONLINE:
			m->text = strdup("online");
			break;
		case SENSOR_DRIVE_IDLE:
			m->text = strdup("idle");
			break;
		case SENSOR_DRIVE_ACTIVE:
			m->text = strdup("active");
			break;
		case SENSOR_DRIVE_REBUILD:
			m->text = strdup("rebuilding");
			break;
		case SENSOR_DRIVE_POWERDOWN:
			m->text = strdup("powering down");
			break;
		case SENSOR_DRIVE_FAIL:
			m->text = strdup("failed");
			break;
		case SENSOR_DRIVE_PFAIL:
			m->text = strdup("degraded");
			break;
		default:
			m->text = strdup("unknown");
			break;
		}
		if (m->text == NULL)
			n = -1;
		break;
	case SENSOR_TIMEDELTA:
		n = asprintf(&m->text, "%.6f secs", ks.value / 1000000000.0);
		break;
	default:
		m->text = strdup("unknown");
		if (m->text == NULL)
			n = -1;
	}
	if (n == -1) {
		m->text = NULL;
		return -1;
	}
	return 0;
}
#endif /* HAS_BSD_SENSORS */
